#include "config.h"
#include "connections.h"
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <windows.h>
#include "files_and_reply.h"
#include "telnet.h"

// The maximum amount of threads
#define MAX_THREADS		20

// The default length of the receiving message buffer
#define DEFAULT_BUF_LEN	512

// The string length required to store a domain name
#define ADDR_LEN		INET_ADDRSTRLEN * sizeof(char)

// Return value for functions whose operation were successfull.
#define SUCCESS			0

// Return value for functions whose operation were NOT successfull.
#define FAILURE			-1

static int acceptConnection(SOCKET listenSocket);
static int closeConnection(SOCKET clientSocket);
static DWORD WINAPI waitForCommand(LPVOID clientSocket);
static DWORD WINAPI listenHTTP(LPVOID lpParam);
static int getFAI(void);
static int getPortNumber(SOCKET socket);
static int initListenSocket(SOCKET *listenSocket, PSTR port);

static HANDLE threads[MAX_THREADS];
static SOCKET listenUserSocket;
static SOCKET listenAdminSocket;

/*
* Function: startHTTPThread
* Usage: iResult = startHTTPThread();
* -----------------------------------
* Starts a thrad to listen for incomming HTTP connections.
*/
int startHTTPThread(){
	int i;

	i = getFAI();
	if (i != FAILURE){
		threads[i] = CreateThread(NULL, 0, listenHTTP, NULL, 0, NULL);
	}
	else{
		printf("Error: Threads array is full...\n");
	}

	return SUCCESS;
}

/*
* Function: initListenSocket()
* Usage: error = initListenSocket();
* --------------------------------
* Initializes the argumenmt listen socket
* and binds it to the argument port at the local address.
*/
static int initListenSocket(SOCKET *listenSocket, PSTR port){
	int iResult;
	ADDRINFOA hints;
	PADDRINFOA result;

	ZeroMemory(&hints, sizeof(hints));
	hints.ai_family		= AF_INET;
	hints.ai_socktype	= SOCK_STREAM;
	hints.ai_protocol	= IPPROTO_TCP;
	iResult = getaddrinfo(NULL, port, &hints, &result);
	if (iResult){
		printf("Error at getaddrinfo(): %d\n", iResult);
		return FAILURE;
	}
	*listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
	if (*listenSocket == SOCKET_ERROR){
		printf("Error at socket(): %d\n", WSAGetLastError());
		freeaddrinfo(result);
		return FAILURE;
	}
	if (bind(*listenSocket, result->ai_addr, (int)result->ai_addrlen)){
		printf("Error at bind(): %d\n", WSAGetLastError());
		freeaddrinfo(result);
		return FAILURE;
	}
	return SUCCESS;
}

/*
* Function: listenHTTP()
* Usage: iResult = listenHTTP();
* --------------------------------------
* Enters a state where the server is listening for incomming HTTP connections.
* Returns FAILURE if any error occured, otherwise the function returns SUCCESS.
*/
static DWORD WINAPI listenHTTP(LPVOID lpParam){
	if (listenUserSocket == (SOCKET)NULL){
		initListenSocket(&listenUserSocket, loadServerValues().httpPort);
	}
	while (isServerActive()){
		if (listen(listenUserSocket, SOMAXCONN) == SOCKET_ERROR) {
			printf("Listen failed with error: %ld\n", WSAGetLastError());
			ExitThread(SUCCESS);
			return FAILURE;
		}
		acceptConnection(listenUserSocket);
	}
	if (listenUserSocket != (SOCKET)NULL){
		if (closesocket(listenUserSocket)){
			printf("Error at closesocket(): %d\n", WSAGetLastError());
		}
		listenUserSocket = (SOCKET)NULL;
	}
	ExitThread(SUCCESS);

	return SUCCESS;
}

/*
* Function: waitForAdminConnections();
* Usage: waitForAdminConnection();
* ---------------------------------
* Enters a state where the server is listening for incomming admin connections.
* Returns FAILURE if any error occured, otherwise the function returns SUCCESS.
*/
int waitForAdminConnections(void){
	if (listenAdminSocket == (SOCKET)NULL){
		initListenSocket(&listenAdminSocket, loadServerValues().telnetPort);
	}
	if (listen(listenAdminSocket, SOMAXCONN) == SOCKET_ERROR){
		printf("Listen failed with error: %ld\n", WSAGetLastError());
		return FAILURE;
	}
	while (TRUE){
		acceptConnection(listenAdminSocket);
	}

	return SUCCESS;
}

/*
* Function: acceptConnection
* Usage: result = acceptConnection();
* --------------------------
* Accepts the connection comming in from the argument listenSocket
* and creates a new thread to handle the user's HTTP/Telnet requests
* (depending on if the listenSocket is bound to the http or telnet port).
* The function returns SUCCESS if the acceptance was successfull.
* Otherwise, the function returns FAILURE.
*/
static int acceptConnection(SOCKET listenSocket){
	int i;
	SOCKET serverSocket;

	serverSocket = INVALID_SOCKET;
	serverSocket = accept(listenSocket, NULL, NULL);
	// Handle socket initiation error.
	if (serverSocket == INVALID_SOCKET){
		printf("Accpet failed: %d\n", WSAGetLastError());
		return FAILURE;
	}
	if (!isServerActive() && getPortNumber(listenSocket) == atoi(loadServerValues().httpPort)){
		return FAILURE;
	}
	// Handle admin commands
	if (getPortNumber(listenSocket) == atoi(loadServerValues().telnetPort)){
		adminClient(serverSocket);
	}
	else{ // Handle http commands
		i = getFAI();
		if (i >= 0){
			threads[i] = CreateThread(NULL, 0, waitForCommand, (LPVOID)serverSocket, 0, NULL);
		}
		else{
			return FAILURE;
		}
	}

	return SUCCESS;
}

/*
* Function: closeConnection
* Usage: result = closeConnection(clientSocket);
* ----------------------------------------------
* Closes the connection with the argument clientSocket.
* The function returns SUCCESS if the closure was successfull.
* Otherwise, the function returns ERROR.
*/
static int closeConnection(SOCKET serverSocket){
	if (shutdown(serverSocket, SD_SEND == SOCKET_ERROR)){
		printf("Error at shutdown(): %d\n", WSAGetLastError());
	}
	if (closesocket(serverSocket)){
		printf("Error at closesocket(): %d\n", WSAGetLastError());
	}
	ExitThread(SUCCESS);

	return SUCCESS;
}

/*
* Function: waitForCommand
* Usage: waitForCommand();
* -----------------------
* Handles the user's HTTP requests.
* The parameter lpParam should be set to the connected socket that communicates with the peer.
*/
static DWORD WINAPI waitForCommand(LPVOID serverSocket){
	int iRecvResult, iSendResult, bufLen, sinLen;
	errorT error;
	char *headBuf = NULL,
		 *fileBuf = NULL,
		 buffer[DEFAULT_BUF_LEN], 
		 domBuf[ADDR_LEN],
		 portBuf[ADDR_LEN];
	struct sockaddr_in sin;

	// Retrieve the peer address
	sinLen = sizeof(sin);
	if (getpeername((SOCKET)serverSocket, (struct sockaddr *)&sin, &sinLen)){
		printf("Retrieving peer addres failed: %d\n", WSAGetLastError());
		closeConnection((SOCKET)serverSocket);
	} // Extract the ip and port number from the peer address
	if (getnameinfo((struct sockaddr*)&sin, sinLen, domBuf, ADDR_LEN, portBuf, ADDR_LEN, NI_NUMERICSERV)){
		printf("Extracting ip and port number from peer address failed: %d\n", WSAGetLastError());
		closeConnection((SOCKET)serverSocket);
	}

	// Receive message from peer
	ZeroMemory(&buffer, sizeof(char) * DEFAULT_BUF_LEN);
	iRecvResult = recv((SOCKET)serverSocket, buffer, DEFAULT_BUF_LEN, 0);
	headBuf	= _strdup(buffer);
	if (iRecvResult > 0) {
		printf("Bytes received: %d\n", iRecvResult);
		bufLen = DEFAULT_BUF_LEN;
		error = getFile(&fileBuf, &bufLen, &headBuf, domBuf, portBuf);
		if (error){
			printf("Parsing request failed: %d\n", error);
		} // Send response file to peer
		if (isServerActive()){
			iSendResult = send((SOCKET)serverSocket, fileBuf, bufLen, 0);
			if (iSendResult == SOCKET_ERROR){
				printf("Send failed: %d\n", WSAGetLastError());
			}
			printf("Bytes sent: %d\n", iSendResult);
		}
	}
	 // Shut down the connection
	printf("Connection closing...\n");
	free(fileBuf);
	free(headBuf);
	closeConnection((SOCKET)serverSocket);

	return SUCCESS;
}

/*
* Function: getFAI
* Usage: i = getFAI();
* --------------------
* Gets the first available index in the static threads array.
* If there is no available index in the threads array,
* this function will return ERROR.
*/
static int getFAI(void){
	int i;

	for (i = 0; i < MAX_THREADS; i++){
		if ((WaitForSingleObject(threads[i], 0) == WAIT_OBJECT_0) || threads[i] == NULL){
			return i;
		}
	}

	return FAILURE;
}

/*
* Function: getPortNumber
* Usage: portNumber = getPortNumber(listenSocket);
* ------------------------------------------------
* Gets the port number bound to the argument clientSocket.
* If the function succeeds, the port number will be returned as an int.
* Otherwise, the function returns ERROR.
*/
static int getPortNumber(SOCKET serverSocket){
	struct sockaddr_in sin;
	int sinLen, iResult;

	sinLen = sizeof(sin);
	iResult = getsockname(serverSocket, (struct sockaddr *)&sin, &sinLen);
	if (iResult != 0 && sin.sin_family != AF_INET || sinLen != sizeof(sin)){
		printf("Retrieving port number failed: %d\n", iResult);
		return FAILURE;
	}

	return ntohs(sin.sin_port);
}